package net.oschina.bilbodai.common.retrofitor;

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * @author daimao  Date: 16/4/14 Time: 下午4:43
 * @version $Id$
 */
public abstract class ClassPathBeanScanner {
    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);

    public Set<BeanDefinition> doScan(String scanPackages){
        String[] packagePaths = StringUtils.tokenizeToStringArray(scanPackages.trim(), ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        for (String basePackage : packagePaths) {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + "/" + DEFAULT_RESOURCE_PATTERN;
            Resource[] resources;
            try {
                resources = this.resourcePatternResolver.getResources(packageSearchPath);
            } catch (IOException e) {
                throw new RuntimeException("getResource Error", e);
            }
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    try {
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                        if (isCandidate(metadataReader)) {
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
                            onCandidateFound(sbd);
                            candidates.add(sbd);
                        }
                    } catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to read candidate component class: " + resource, ex);
                    }
                }
            }
        }
        return candidates;
    }

    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(basePackage);
    }

    public abstract void onCandidateFound(BeanDefinition beanDefinition) throws Exception;

    public abstract boolean isCandidate(MetadataReader metadataReader);
}